import Java from "frida-java-bridge";
import { Logger } from "./log.js";

const Tag = "java";

export function searchClassNamefromClassLoader(
  classloader: any,
  patten: string,
) {
  var className = "";

  try {
    var DexFile = Java.use("dalvik.system.DexFile");
    var BaseDexClassLoader = Java.use("dalvik.system.BaseDexClassLoader");
    var DexPathList = Java.use("dalvik.system.DexPathList");
    var Element = Java.use("dalvik.system.DexPathList$Element");
    var basedexclassloaderobj = Java.cast(classloader, BaseDexClassLoader);

    var tmpobj = basedexclassloaderobj.pathList.value;
    var pathlistobj = Java.cast(tmpobj, DexPathList);
    var dexElementsobj = pathlistobj.dexElements.value;

    if (dexElementsobj == null) {
      return className;
    }

    for (var i in dexElementsobj) {
      var obj = dexElementsobj[i];
      var elementobj = Java.cast(obj, Element);
      tmpobj = elementobj.dexFile.value;
      var dexfileobj = Java.cast(tmpobj, DexFile);
      const enumeratorClassNames = dexfileobj.entries();
      while (enumeratorClassNames.hasMoreElements()) {
        var name = enumeratorClassNames.nextElement().toString();
        if (name.includes(patten)) {
          className = name;
          break;
        }
      }
    }
  } catch (e) {
    console.log(e);
  }

  return className;
}

export function useByClassLoader(loader: any, className: string) {
  var clazz = null;

  if (
    className == "" ||
    className == null ||
    className == undefined ||
    className == "null"
  ) {
    return clazz;
  }

  try {
    var classFactory = Java.ClassFactory.get(loader);
    clazz = classFactory.use(className);
  } catch (error) {
    Logger.w(Tag, String(error));
  }

  return clazz;
}

/**
 * Java 相关的函数
 */
export const JavaHelper = {
  /**
   * Java.use 的加强版
   * 传入类名，自动加载。
   */
  useClass(className: string) {
    var clazz = null;

    // 默认类加载器
    try {
      clazz = Java.use(className);
      Logger.i(Tag, "load class by default -> " + className);
      return clazz;
    } catch (error) {
      Logger.w(Tag, String(error));
    }

    // 多个类加载器
    var classLoaders = Java.enumerateClassLoadersSync();
    for (var i = 0; i < classLoaders.length; i++) {
      var loader = classLoaders[i];
      clazz = useByClassLoader(loader, className);
      if (clazz != null) {
        Logger.i(Tag, "load class by classloader -> " + className);
        return clazz;
      }
    }

    // 加固
    var application = Java.use("android.app.Application");
    application.attach.override("android.content.Context").implementation =
      function (context: any) {
        var result = this.attach(context);
        var loader = context.getClassLoader();
        clazz = useByClassLoader(loader, className);
        return result;
      };

    if (clazz != null) {
      Logger.i(Tag, "load class by attach -> " + className);
    }

    return clazz;
  },

  /**
   * 根据类名的一部分，搜索类，返回类引用。
   */
  searchClass(patten: string) {
    var clazz = null;

    // 默认类加载器
    let classes = Java.enumerateLoadedClassesSync();
    for (var i = 0; i < classes.length; i++) {
      let name = classes[i];
      if (name.includes(patten)) {
        clazz = Java.use(name);
        break;
      }
    }

    if (clazz) {
      return clazz;
    }

    // 多个类加载器
    var classLoaders = Java.enumerateClassLoadersSync();
    for (var i = 0; i < classLoaders.length; i++) {
      var loader = classLoaders[i];
      let className = searchClassNamefromClassLoader(loader, patten);
      clazz = useByClassLoader(loader, className);
    }
    if (clazz) {
      return clazz;
    }

    // 加固
    var application = Java.use("android.app.Application");
    application.attach.override("android.content.Context").implementation =
      function (context: any) {
        var result = this.attach(context);
        let loader = context.getClassLoader();
        let className = searchClassNamefromClassLoader(loader, patten);
        clazz = useByClassLoader(loader, className);

        return result;
      };

    return clazz;
  },

  use(className: string, callback: (clz: Java.Wrapper<{}>) => void) {
    var clazz = this.useClass(className);
    if (clazz == null) {
      return;
    }
    callback(clazz);
  },

  searchClassAndUse(patten: string, callback: (clz: Java.Wrapper<{}>) => void) {
    var clazz = this.searchClass(patten);
    if (clazz == null) {
      return;
    }
    callback(clazz);
  },
} as const;
